Homeโ€บsecurity-level
DRAFT

security-level

JSON

๐Ÿ’ก Ide & Konsep

Tingkat Keamanan Ekosistem Zomet

Audit statis atas 29 repo di ~/AppData/Local/hermes/repos, 7 Nov 2026.

Metode: pembacaan kode (rg/grep), bukan uji serang ke sistem berjalan.

Skor

| Aspek | Nilai | Catatan |

|---|---|---|

| Isolasi tenant & hashing password | B | scoped query + bcrypt/argon2 di app utama |

| Manajemen rahasia | D | token di URL git, keystore di repo, secret hardcoded |

| Ketahanan brute force | E | tidak ada rate limit di satu pun API |

| Header keamanan web | E | tanpa helmet/CSP/HSTS |

| Penanganan gagal-konfigurasi | D | fail-open bila env kosong |

| Keseluruhan | Cโˆ’ | fondasi ada, rahasia bocor |

Empat temuan kritis

1. Password Gitea di URL remote semua repo โ€” http://<user>:<token>@103.93.129.94:3001/...

tersimpan di .git/config tiap repo. Bocor satu = kendali seluruh 50 repo.

2. **Keystore + password signing pajak-kb-admin ter-commit** โ€” keystore.properties

memuat storePassword/keyPassword polos. Siapa pun bisa menerbitkan APK yang

diakui sebagai pembaruan resmi app itu.

3. Secret cadangan hardcoded dan masih diterima โ€” z-ecosystem-admin-2026 di

zone-repo/src/lib/secrets.ts, zxroom/lib/auth.ts, zx-carwash-api/src/routes/{admin,sso}.js.

4. Fail-open โ€” memberkit-api membuka seluruh API bila DEVICE_TOKEN kosong;

Z-Rooms/lib/jwt.ts memakai 'fallback-secret' bila NEXTAUTH_SECRET hilang.

Yang sudah benar

  • bcrypt/argon2: zx-carwash-api, acumedis-api, zone-repo, zpos
  • crypto.timingSafeEqual untuk membandingkan secret lintas-app
  • JWT SSO berumur 300 detik
  • addHook('preHandler', requireAuth) global di zx-carwash-api
  • .env tidak pernah ter-commit (hanya .env.example)

Urutan kerja

| # | Langkah | Dampak | Estimasi |

|---|---|---|---|

| 1 | Rotasi token Gitea, pindah ke credential helper | kendali repo | 30 mnt |

| 2 | Keluarkan keystore dari git, ganti password | tanda tangan APK | 1 jam |

| 3 | Hapus OLD_SECRET dari 4 berkas | admin lintas-app | 30 mnt |

| 4 | Fail-closed bila env wajib kosong | akses tak berizin | 15 mnt |

| 5 | Rate limit login 5/menit per IP+email | brute force | 1 jam |

| 6 | Helmet + CSP + HSTS | clickjacking/downgrade | 1 jam |

Langkah 1โ€“4 adalah rahasia yang sudah bocor: memperbaiki kode tidak cukup,

nilainya harus diganti.

Aturan yang dipegang saat mengerjakan

  • Rahasia asli tidak ditulis di halaman ini โ€” blueprint ini tanpa autentikasi.
  • Setiap langkah diverifikasi dengan bukti (commit, deploy, tes), bukan klaim.
  • Tidak ada perubahan infra/DB tanpa persetujuan pemilik.

Ringkasan temuan

Skor per proyek

| Proyek | Jenis | Risiko utama |

|---|---|---|

| zone-repo (Z One) | Next.js + Prisma | OLD_SECRET hardcoded di src/lib/secrets.ts |

| zxroom / Z-Rooms | Next.js | OLD_SECRET hardcoded + fallback-secret di lib/jwt.ts |

| zx-carwash-api | Fastify | OLD_SECRET di routes/admin.js + routes/sso.js |

| zx-carwash (app) | Android | outbox retry tanpa batas (terlindung clientId) |

| memberkit-api | Node polos | fail-open: DEVICE_TOKEN kosong = API terbuka |

| pajak-kb-api | Next.js | JWT tanpa rotasi kunci, cron pakai JWT_SECRET yang sama |

| pajak-kb-admin | Android | keystore + password di repo |

| acumedis-api | Fastify | hashing benar, tanpa rate limit |

| ytvault-api | Python | token statis bersama, bukan per-pengguna |

| zpos, zxgym, Zadv, zomet-main | Next.js | tanpa CSP/HSTS |

Seluruh 29 repo memakai remote yang memuat token pribadi di URL.

Rahasia yang sudah bocor

Harus diganti, bukan ditambal

1. Token Gitea di URL remote

git remote get-url origin
โ†’ http://djvpri:<token>@103.93.129.94:3001/djvpri/<repo>.git

git remote get-url origin

โ†’ http://djvpri:<token>@103.93.129.94:3001/djvpri/<repo>.git


Ada di `.git/config` **setiap** repo. Token ini bisa membuat, menghapus, dan
menimpa seluruh repo โ€” termasuk memaksa auto-deploy Coolify berjalan.

**Tindakan:** cabut token lama di Gitea, buat token baru, simpan lewat
credential helper (`git config --global credential.helper manager`), ubah remote
menjadi `http://103.93.129.94:3001/djvpri/<repo>.git` tanpa user:token.

### 2. Keystore `pajak-kb-admin` di dalam git

Ada di .git/config setiap repo. Token ini bisa membuat, menghapus, dan

menimpa seluruh repo โ€” termasuk memaksa auto-deploy Coolify berjalan.

Tindakan: cabut token lama di Gitea, buat token baru, simpan lewat

credential helper (git config --global credential.helper manager), ubah remote

menjadi http://103.93.129.94:3001/djvpri/<repo>.git tanpa user:token.

2. Keystore `pajak-kb-admin` di dalam git

git show HEAD:keystore.properties
  storePassword=<password polos>
  keyAlias=pajakkbadmin
  keyPassword=<password polos>

git show HEAD:keystore.properties

storePassword=<password polos>

keyAlias=pajakkbadmin

keyPassword=<password polos>


**Tindakan:** keluarkan `keystore.properties` + `app/keystore/release.jks` dari
repo (`git rm --cached`), tambahkan ke `.gitignore`, pindahkan JKS ke
`C:/Users/Anjuv/keystores/`, ganti password keystore, lalu terbitkan APK baru
dengan kunci itu. Catat: APK lama tetap sah sampai pengguna memasang versi baru.

### 3. `z-ecosystem-admin-2026`

Diterima sebagai secret cadangan di:

Tindakan: keluarkan keystore.properties + app/keystore/release.jks dari

repo (git rm --cached), tambahkan ke .gitignore, pindahkan JKS ke

C:/Users/Anjuv/keystores/, ganti password keystore, lalu terbitkan APK baru

dengan kunci itu. Catat: APK lama tetap sah sampai pengguna memasang versi baru.

3. `z-ecosystem-admin-2026`

Diterima sebagai secret cadangan di:

zone-repo/src/lib/secrets.ts:8
zxroom/lib/auth.ts:36
Z-Rooms/lib/auth.ts:36
zx-carwash-api/src/routes/admin.js:29
zx-carwash-api/src/routes/sso.js:36

zone-repo/src/lib/secrets.ts:8

zxroom/lib/auth.ts:36

Z-Rooms/lib/auth.ts:36

zx-carwash-api/src/routes/admin.js:29

zx-carwash-api/src/routes/sso.js:36


**Tindakan:** hapus semua baris `OLD_SECRET`, sisakan satu sumber
(`CROSS_APP_SECRET` dari env). Bila env kosong โ†’ **tolak start**, jangan diam.

### 4. Fail-open

Tindakan: hapus semua baris OLD_SECRET, sisakan satu sumber

(CROSS_APP_SECRET dari env). Bila env kosong โ†’ tolak start, jangan diam.

4. Fail-open

memberkit-api/server.js:7    const TOKEN = process.env.DEVICE_TOKEN || ''
memberkit-api/server.js:93   if (TOKEN && ...) return 401     โ† env kosong = lolos
Z-Rooms/lib/jwt.ts:6         process.env.NEXTAUTH_SECRET || 'fallback-secret'

memberkit-api/server.js:7 const TOKEN = process.env.DEVICE_TOKEN || ''

memberkit-api/server.js:93 if (TOKEN && ...) return 401 โ† env kosong = lolos

Z-Rooms/lib/jwt.ts:6 process.env.NEXTAUTH_SECRET || 'fallback-secret'


**Tindakan:** ganti menjadi gagal saat start bila env wajib tidak ada.

Tindakan: ganti menjadi gagal saat start bila env wajib tidak ada.

Celah tingkat tinggi

Belum ada rate limit

zx-carwash-api   0 pembatas
acumedis-api     0
memberkit-api    0
ytvault-api      0
pajak-kb-api     0

zx-carwash-api 0 pembatas

acumedis-api 0

memberkit-api 0

ytvault-api 0

pajak-kb-api 0


Endpoint login bisa dicoba ribuan kali tanpa hambatan; endpoint yang memanggil
Gemini/yt-dlp bisa dipakai menghabiskan kuota Anda.

**Konsep:** pembatas per `IP + email`, 5 percobaan/menit, jeda bertambah setelah
10 kegagalan. Simpan di memori proses (satu instance per app โ€” cukup), tanpa Redis.

## Belum ada header keamanan

Endpoint login bisa dicoba ribuan kali tanpa hambatan; endpoint yang memanggil

Gemini/yt-dlp bisa dipakai menghabiskan kuota Anda.

Konsep: pembatas per IP + email, 5 percobaan/menit, jeda bertambah setelah

10 kegagalan. Simpan di memori proses (satu instance per app โ€” cukup), tanpa Redis.

Belum ada header keamanan

helmet / CSP / X-Frame-Options / HSTS โ†’ 0 hasil di 9 proyek web

helmet / CSP / X-Frame-Options / HSTS โ†’ 0 hasil di 9 proyek web


**Konsep:** satu middleware bersama. `X-Frame-Options: DENY` untuk halaman
aplikasi, `frame-ancestors 'none'`, `Strict-Transport-Security: max-age=31536000`,
dan CSP longgar dulu (laporkan pelanggaran, baru perketat) agar tidak mematahkan
halaman yang ada.

Konsep: satu middleware bersama. X-Frame-Options: DENY untuk halaman

aplikasi, frame-ancestors 'none', Strict-Transport-Security: max-age=31536000,

dan CSP longgar dulu (laporkan pelanggaran, baru perketat) agar tidak mematahkan

halaman yang ada.

Risiko tingkat sedang

Token YTVault dipakai bersama

ytvault-api memakai satu API_TOKEN statis untuk semua pemasangan. Bocor dari

satu APK = siapa pun bisa memakai server Anda untuk mengunduh.

Konsep: token per pemasangan (dibuat saat pertama kali app dijalankan),

disimpan server, bisa dicabut satu per satu di Setelan.

Isolasi tenant bergantung disiplin query

Semua findMany di zx-carwash-api sudah ber-tenantId, tetapi **tidak ada

constraint di DB** yang memaksa. Satu rute baru yang lupa menyaring = data

lintas tenant terbaca.

Konsep: bungkus akses Prisma dalam helper db.tenant(tenantId) yang selalu

menyisipkan filter โ€” bukan mengandalkan ingatan penulis kode.

Cron memakai kunci yang sama dengan sesi

pajak-kb-api/src/app/api/cron/expire/route.ts memakai JWT_SECRET sebagai

header cron. Bocor satu = bisa memicu job apa pun.

Konsep: kunci terpisah CRON_SECRET, hanya untuk cron, tanpa hak membuat sesi.

Rencana kerja bertahap

Tahap 0 โ€” persiapan (10 menit)

  • Catat semua remote repo sebelum diubah (git remote -v > remote-lama.txt).
  • Pastikan punya salinan keystore dan password baru di tempat aman.

Tahap 1 โ€” rahasia yang bocor

| # | Langkah | Verifikasi |

|---|---|---|

| 1 | Cabut token Gitea lama, buat baru | token lama โ†’ 401 |

| 2 | Ubah remote tanpa token + credential helper | git fetch jalan tanpa user:token di URL |

| 3 | Keluarkan JKS + keystore.properties dari git | git ls-files tidak memuatnya |

| 4 | Ganti password keystore, terbitkan APK baru | apksigner verify + pasang di HP |

| 5 | Hapus OLD_SECRET di 5 berkas, wajibkan env | app menolak start bila env kosong |

| 6 | Fail-closed memberkit-api & Z-Rooms | API menolak saat env kosong |

Tahap 2 โ€” pengerasan

| # | Langkah | Verifikasi |

|---|---|---|

| 7 | Rate limit 5/menit per IP+email di 5 API | percobaan ke-6 โ†’ 429 |

| 8 | Helmet + CSP lapor + HSTS di 9 app web | header terlihat di curl -I |

| 9 | Token cron terpisah (CRON_SECRET) | header lama โ†’ 401 |

| 10 | Token YTVault per pemasangan | satu token dicabut, sisanya jalan |

Tahap 3 โ€” struktural (opsional)

  • Helper db.tenant(tenantId) di zx-carwash-api, acumedis-api, memberkit-api.
  • Uji regresi: satu tes per app yang memastikan permintaan tanpa token โ†’ 401,

dan permintaan tenant A tidak melihat data tenant B.

Aturan pelaksanaan

1. Satu langkah per commit, satu verifikasi nyata per langkah.

2. Tidak ada perubahan DB/infra tanpa persetujuan.

3. Setiap langkah dicatat hasilnya di halaman ini.

Catatan metode

Yang diperiksa

  • rg untuk pola rahasia, hashing, rate limit, header, timing-safe compare.
  • git ls-files untuk mencari berkas kredensial yang ter-commit.
  • git remote get-url untuk melihat token di URL.
  • Pembacaan preHandler/middleware untuk memastikan auth global.

Yang **tidak** diperiksa

  • Uji serang ke sistem berjalan (tidak dilakukan โ€” hanya analisis statis).
  • Konfigurasi Coolify, firewall VPS, dan sertifikat TLS.
  • Dependensi usang (perlu npm audit terpisah per repo).
  • Keamanan di sisi perangkat (root, APK sideload, penyimpanan lokal terenkripsi).

Skor di halaman ini adalah penilaian atas kode yang terbaca, bukan jaminan

bahwa sistem aman. Bagian yang tidak diperiksa di atas dapat mengubah gambaran.

Temuan lanjutan: server & dependensi

Ditemukan pada pemeriksaan lanjutan (7 Nov, malam)

Saya periksa hal-hal yang belum masuk audit pertama: port server, dependensi,

SSRF, dan isolasi runtime. Hasilnya menambah satu temuan kritis baru dan

satu masalah serius yang tidak terlihat dari kode.

K1 โ€” SELURUH PORT TERBUKA KE INTERNET, TANPA FIREWALL

ss -tlnp di VPS
  0.0.0.0:22     sshd
  0.0.0.0:5432   docker-proxy  โ†’ container nginx zpos
  0.0.0.0:3001   gitea
  0.0.0.0:8000   coolify

ufw status          โ†’ inactive
iptables -S INPUT   โ†’ -P INPUT ACCEPT

ss -tlnp di VPS

0.0.0.0:22 sshd

0.0.0.0:5432 docker-proxy โ†’ container nginx zpos

0.0.0.0:3001 gitea

0.0.0.0:8000 coolify

ufw status โ†’ inactive

iptables -S INPUT โ†’ -P INPUT ACCEPT


**Artinya:** Coolify (panel yang bisa men-deploy apa pun), Gitea (sumber semua
kode), dan SSH dapat dijangkau dari internet mana pun. Tidak ada satu pun aturan
firewall. Satu password lemah = seluruh ekosistem jatuh.

Port **5432** terbuka karena container proxy milik app `zpos` memetakan
`5432 -> 0.0.0.0:5432`. Itu pemetaan yang tidak perlu: proxy Coolify seharusnya
hanya mendengarkan 80/443.

**Konsep perbaikan:**

| # | Langkah | Verifikasi |
|---|---|---|
| 1 | `ufw default deny incoming` + `allow 22,80,443` | `nmap` dari luar: hanya 3 port |
| 2 | Batasi panel: Coolify 8000 & Gitea 3001 hanya via SSH tunnel atau IP kantor | `curl` dari luar โ†’ connection refused |
| 3 | Hapus pemetaan `5432:5432` pada container proxy zpos | `ss -tlnp` tidak memuat 5432 |
| 4 | SSH: matikan login password, wajib kunci | `ssh -o PreferredAuthentications=password` โ†’ ditolak |
| 5 | fail2ban untuk SSH & Gitea | 5 gagal login โ†’ IP diblokir 1 jam |

Ini **tidak** memerlukan perubahan kode aplikasi sama sekali, dan dampaknya
paling besar dibanding semua langkah lain.

### K2 โ€” Dependensi: 3 kerentanan kritis `next` + RCE di `next/og`

Hasil `npm audit --omit=dev` per app:

| App | Kritis | High | Yang paling berbahaya |
|---|---|---|---|
| zone-repo | 3 | 5 | `next` middleware bypass, `@auth/core` normalisasi email |
| zxroom / Z-Rooms | 1 | 4 | **`next` RCE di `next/og` ImageResponse** |
| zxgym | 2 | 3 | `@auth/core`, `next` DoS |
| zpos | 1 | 6 | `next` middleware bypass, `xlsx` prototype pollution |
| Zadv | 1 | 4 | `next` DoS |
| zomet-main | 1 | 5 | `next` DoS, `js-yaml` ReDoS |
| pajak-kb-api | 0 | 0 | 6 moderate |
| acumedis-api | 0 | 2 | `fastify`/`find-my-way` DDoS |
| zx-carwash-api | 0 | 2 | `find-my-way` DDoS |

`s-next` versi lama dipakai di `zomet-main` (`^14.2.0`), `zxgym` (`14.2.35`).
Dua app memakai `next@16.3.4` yang masih kena RCE `next/og`.

**Konsep:** naikkan satu per satu dengan urutan risiko: `zxroom`, `Z-Rooms`,
`zone-repo` (paling terekspos publik) lebih dulu. `npm audit fix` biasa dulu;
`--force` hanya bila mayor, dan itu perlu uji halaman.

### K3 โ€” SSRF & pemakaian kuota di ytvault-api

Artinya: Coolify (panel yang bisa men-deploy apa pun), Gitea (sumber semua

kode), dan SSH dapat dijangkau dari internet mana pun. Tidak ada satu pun aturan

firewall. Satu password lemah = seluruh ekosistem jatuh.

Port 5432 terbuka karena container proxy milik app zpos memetakan

5432 -> 0.0.0.0:5432. Itu pemetaan yang tidak perlu: proxy Coolify seharusnya

hanya mendengarkan 80/443.

Konsep perbaikan:

| # | Langkah | Verifikasi |

|---|---|---|

| 1 | ufw default deny incoming + allow 22,80,443 | nmap dari luar: hanya 3 port |

| 2 | Batasi panel: Coolify 8000 & Gitea 3001 hanya via SSH tunnel atau IP kantor | curl dari luar โ†’ connection refused |

| 3 | Hapus pemetaan 5432:5432 pada container proxy zpos | ss -tlnp tidak memuat 5432 |

| 4 | SSH: matikan login password, wajib kunci | ssh -o PreferredAuthentications=password โ†’ ditolak |

| 5 | fail2ban untuk SSH & Gitea | 5 gagal login โ†’ IP diblokir 1 jam |

Ini tidak memerlukan perubahan kode aplikasi sama sekali, dan dampaknya

paling besar dibanding semua langkah lain.

K2 โ€” Dependensi: 3 kerentanan kritis `next` + RCE di `next/og`

Hasil npm audit --omit=dev per app:

| App | Kritis | High | Yang paling berbahaya |

|---|---|---|---|

| zone-repo | 3 | 5 | next middleware bypass, @auth/core normalisasi email |

| zxroom / Z-Rooms | 1 | 4 | **next RCE di next/og ImageResponse** |

| zxgym | 2 | 3 | @auth/core, next DoS |

| zpos | 1 | 6 | next middleware bypass, xlsx prototype pollution |

| Zadv | 1 | 4 | next DoS |

| zomet-main | 1 | 5 | next DoS, js-yaml ReDoS |

| pajak-kb-api | 0 | 0 | 6 moderate |

| acumedis-api | 0 | 2 | fastify/find-my-way DDoS |

| zx-carwash-api | 0 | 2 | find-my-way DDoS |

s-next versi lama dipakai di zomet-main (^14.2.0), zxgym (14.2.35).

Dua app memakai next@16.3.4 yang masih kena RCE next/og.

Konsep: naikkan satu per satu dengan urutan risiko: zxroom, Z-Rooms,

zone-repo (paling terekspos publik) lebih dulu. npm audit fix biasa dulu;

--force hanya bila mayor, dan itu perlu uji halaman.

K3 โ€” SSRF & pemakaian kuota di ytvault-api

def info(request, url: str):
    auth(request)
    y.extract_info(url, download=False)     # url dari klien, tanpa batas

def info(request, url: str):

auth(request)

y.extract_info(url, download=False) # url dari klien, tanpa batas


`url` diterima apa adanya. yt-dlp mengikuti URL itu; tanpa allowlist, server Anda
bisa dipakai mengambil alamat internal (`http://127.0.0.1`, `10.0.1.x`, metadata
cloud) atau menghabiskan kuota/disk.

**Konsep:** tolak host selain `youtube.com`/`youtu.be`/`music.youtube.com`,
batasi panjang URL, batasi 1 job per menit, dan bersihkan berkas job lama
otomatis.

### K4 โ€” Token Gitea masih di dalam riwayat commit?

Perlu diperiksa โ€” token di URL remote tidak masuk commit, tetapi `keystore.properties`
**sudah** masuk. Mengeluarkannya dari HEAD tidak menghapusnya dari riwayat;
siapa pun yang punya klon lama masih bisa membacanya.

**Konsep:** anggap password keystore itu **sudah bocor** dan ganti. Untuk riwayat,
cukup: repo Gitea bersifat privat dan hanya Anda yang punya akses, jadi
penulisan ulang riwayat (`filter-repo`) tidak wajib โ€” tetapi password tetap
harus diganti.

### Ringkasan prioritas baru

url diterima apa adanya. yt-dlp mengikuti URL itu; tanpa allowlist, server Anda

bisa dipakai mengambil alamat internal (http://127.0.0.1, 10.0.1.x, metadata

cloud) atau menghabiskan kuota/disk.

Konsep: tolak host selain youtube.com/youtu.be/music.youtube.com,

batasi panjang URL, batasi 1 job per menit, dan bersihkan berkas job lama

otomatis.

K4 โ€” Token Gitea masih di dalam riwayat commit?

Perlu diperiksa โ€” token di URL remote tidak masuk commit, tetapi keystore.properties

sudah masuk. Mengeluarkannya dari HEAD tidak menghapusnya dari riwayat;

siapa pun yang punya klon lama masih bisa membacanya.

Konsep: anggap password keystore itu sudah bocor dan ganti. Untuk riwayat,

cukup: repo Gitea bersifat privat dan hanya Anda yang punya akses, jadi

penulisan ulang riwayat (filter-repo) tidak wajib โ€” tetapi password tetap

harus diganti.

Ringkasan prioritas baru

P0  Firewall VPS + tutup 5432 + batasi panel    (tanpa ubah kode, 30 menit)
P1  Rotasi token Gitea & password keystore
P2  Hapus OLD_SECRET + fail-closed
P3  Rate limit + header keamanan
P4  Naikkan dependensi (next, @auth/core, fastify)
P5  SSRF allowlist + token per pemasangan

P0 Firewall VPS + tutup 5432 + batasi panel (tanpa ubah kode, 30 menit)

P1 Rotasi token Gitea & password keystore

P2 Hapus OLD_SECRET + fail-closed

P3 Rate limit + header keamanan

P4 Naikkan dependensi (next, @auth/core, fastify)

P5 SSRF allowlist + token per pemasangan

Backup & pemulihan (koreksi temuan)

Temuan: tidak ada backup sama sekali โ†’ dikoreksi

Saya awalnya melaporkan "backup tidak ada". Itu salah. Saya hanya membaca

crontab root dan melewatkan jadwal backup yang hidup di dalam Coolify.

Koreksi ini hasil pemeriksaan langsung ke Cloudflare R2, bukan pembacaan kode.

Yang SEBENARNYA sudah jalan

bucket      zomet @ 0d2e...r2.cloudflarestorage.com (Cloudflare R2)
objek R2    342 ยท kredensial masih sah (listObjectsV2 berhasil diuji)
dump lokal  /data/coolify/backups โ€” 3,4 GB
riwayat     570 sukses ยท 4 gagal

bucket zomet @ 0d2e...r2.cloudflarestorage.com (Cloudflare R2)

objek R2 342 ยท kredensial masih sah (listObjectsV2 berhasil diuji)

dump lokal /data/coolify/backups โ€” 3,4 GB

riwayat 570 sukses ยท 4 gagal


| Database | Jadwal | Ke R2 | Terakhir |
|---|---|---|---|
| ZPos `1fhosrzrapewqtv4wkqiw1rs` | `0 */3 * * *` | โœ“ | 10 Nov 06:01 |
| Z One `jznfq1n9zkfdqtjcjwbcnxwb` | `0 20 * * *` | โœ“ | 9 Nov 20:00 |
| ZX Gym `wko4doynkswavl3gqibxchge` | `0 20 * * *` | โœ“ | 9 Nov 20:00 |

### Yang MASIH BOLONG

**Tidak punya jadwal backup sama sekali:**

| Database | Dampak |
|---|---|
| **AcuMedis** `shlrypoiye8df6ijn99lnbii` | data pasien โ€” paling serius |
| **ZX Carwash** `9zrumir0odqrm3zoaxkh56wg` | transaksi, bon, antrian, kas harian |
| **ZXRoom** `adyf5mac2kjppwuoqebk7naf` | properti & penyewaan |
| **pajak-kb-api** `znoe26p9` | data wajib pajak |
| MemberKit `e006bspftf2mjdmpbisks2k9` | member & tenant |
| ytvault | kecil, tapi tanpa salinan |

**Di luar Postgres โ€” tidak ada mekanisme:**

- Repo Gitea (bind mount `/root/gitea/data`) โ€” **semua kode sumber**
- Konfigurasi Coolify (`.env`, compose, volume `coolify-db`)
- `.env` semua app (rahasia produksi; bocor = harus rotasi)
- Berkas unduhan YTVault (besar, bisa diunduh ulang)

**Temuan sampingan:** dari 334 objek ZPos di R2, ~320 bukan dump database โ€”
itu materi LLM Zadv yang tersimpan di jalur lama. Dump asli hanya ~14 terakhir.

### Rencana perbaikan

Tidak perlu skrip baru maupun ubah kode. Mekanisme yang sama sudah terbukti
jalan untuk ZPos tiap 3 jam; yang kurang hanya mengisi jadwal di panel Coolify.

| # | Database | Frekuensi | Ke S3 |
|---|---|---|---|
| 1 | ZX Carwash | `0 */3 * * *` (3 jam) | โœ“ |
| 2 | AcuMedis | `0 20 * * *` | โœ“ |
| 3 | ZXRoom | `0 20 * * *` | โœ“ |
| 4 | pajak-kb-api | `0 20 * * *` | โœ“ |
| 5 | MemberKit | `0 20 * * *` | โœ“ |
| 6 | ytvault | `0 20 * * *` | โœ“ |

Setelah keenam jadwal ini hidup, keenamnya mengikuti jalur yang sudah terbukti
(570 sukses) โ€” tidak ada komponen baru yang bisa gagal.

**Tahap berikutnya** (belum dikerjakan, perlu keputusan terpisah):

| # | Langkah | Alasan |
|---|---|---|
| 7 | `tar` harian repo Gitea โ†’ R2 | semua kode sumber ada di situ |
| 8 | Backup `.env` terenkripsi โ†’ R2 | tanpa ini, DB saja tidak cukup untuk memulihkan app |
| 9 | Uji restore satu DB ke container sementara | backup yang tak pernah diuji bukan backup |

### Aturan masa depan untuk database baru

Setiap kali app baru dibuat, **jadwal backupnya dibuat di hari yang sama.**
Dua kali sekarang DB baru lahir tanpa backup (ZX Carwash, AcuMedis). Lebih baik
menjadikannya bagian dari daftar pembuatan app daripada mengandalkan ingatan.

### Status

Menunggu perintah. Belum ada jadwal yang dibuat dan belum ada berkas yang
diubah.

| Database | Jadwal | Ke R2 | Terakhir |

|---|---|---|---|

| ZPos 1fhosrzrapewqtv4wkqiw1rs | 0 */3 * * * | โœ“ | 10 Nov 06:01 |

| Z One jznfq1n9zkfdqtjcjwbcnxwb | 0 20 * * * | โœ“ | 9 Nov 20:00 |

| ZX Gym wko4doynkswavl3gqibxchge | 0 20 * * * | โœ“ | 9 Nov 20:00 |

Yang MASIH BOLONG

Tidak punya jadwal backup sama sekali:

| Database | Dampak |

|---|---|

| AcuMedis shlrypoiye8df6ijn99lnbii | data pasien โ€” paling serius |

| ZX Carwash 9zrumir0odqrm3zoaxkh56wg | transaksi, bon, antrian, kas harian |

| ZXRoom adyf5mac2kjppwuoqebk7naf | properti & penyewaan |

| pajak-kb-api znoe26p9 | data wajib pajak |

| MemberKit e006bspftf2mjdmpbisks2k9 | member & tenant |

| ytvault | kecil, tapi tanpa salinan |

Di luar Postgres โ€” tidak ada mekanisme:

  • Repo Gitea (bind mount /root/gitea/data) โ€” semua kode sumber
  • Konfigurasi Coolify (.env, compose, volume coolify-db)
  • .env semua app (rahasia produksi; bocor = harus rotasi)
  • Berkas unduhan YTVault (besar, bisa diunduh ulang)

Temuan sampingan: dari 334 objek ZPos di R2, ~320 bukan dump database โ€”

itu materi LLM Zadv yang tersimpan di jalur lama. Dump asli hanya ~14 terakhir.

Rencana perbaikan

Tidak perlu skrip baru maupun ubah kode. Mekanisme yang sama sudah terbukti

jalan untuk ZPos tiap 3 jam; yang kurang hanya mengisi jadwal di panel Coolify.

| # | Database | Frekuensi | Ke S3 |

|---|---|---|---|

| 1 | ZX Carwash | 0 */3 * * * (3 jam) | โœ“ |

| 2 | AcuMedis | 0 20 * * * | โœ“ |

| 3 | ZXRoom | 0 20 * * * | โœ“ |

| 4 | pajak-kb-api | 0 20 * * * | โœ“ |

| 5 | MemberKit | 0 20 * * * | โœ“ |

| 6 | ytvault | 0 20 * * * | โœ“ |

Setelah keenam jadwal ini hidup, keenamnya mengikuti jalur yang sudah terbukti

(570 sukses) โ€” tidak ada komponen baru yang bisa gagal.

Tahap berikutnya (belum dikerjakan, perlu keputusan terpisah):

| # | Langkah | Alasan |

|---|---|---|

| 7 | tar harian repo Gitea โ†’ R2 | semua kode sumber ada di situ |

| 8 | Backup .env terenkripsi โ†’ R2 | tanpa ini, DB saja tidak cukup untuk memulihkan app |

| 9 | Uji restore satu DB ke container sementara | backup yang tak pernah diuji bukan backup |

Aturan masa depan untuk database baru

Setiap kali app baru dibuat, jadwal backupnya dibuat di hari yang sama.

Dua kali sekarang DB baru lahir tanpa backup (ZX Carwash, AcuMedis). Lebih baik

menjadikannya bagian dari daftar pembuatan app daripada mengandalkan ingatan.

Status

Menunggu perintah. Belum ada jadwal yang dibuat dan belum ada berkas yang

diubah.